
/**
  ******************************************************************************
  * Copyright 2021 The Microbee Authors. All Rights Reserved.
  * 
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
  * 
  * http://www.apache.org/licenses/LICENSE-2.0
  * 
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
  * 
  * @file       blog.c
  * @author     baiyang
  * @date       2021-8-6
  ******************************************************************************
  */

/*----------------------------------include-----------------------------------*/
#include <string.h>
#include <stdio.h>

#include <rtthread.h>

#if CONFIG_HAL_BOARD != HAL_BOARD_SITL_WIN
#include <unistd.h>
#endif

#include "blog.h"
#include "blog_interface.h"
#include "ap_log.h"

#include <parameter/param.h>
#include <common/gp_defines.h>
#include <common/time/gp_time.h>
#include <common/console/console.h>
#include <file_manager/file_manager.h>
/*-----------------------------------macro------------------------------------*/

/*----------------------------------typedef-----------------------------------*/

/*---------------------------------prototype----------------------------------*/

/*----------------------------------variable----------------------------------*/
static param_t var_info[] = {
    /* Determines when to start and stop logging (Blog).
    0: when armed, start logging,when disarm, stop
    1: when armed, start logging,when Power-off,stop
    2: when Power-on, start logging*/
    PARAM_DEFINE_INT32(BLOG_MODE, 0),

    PARAM_DEFINE_UINT16(BLOG_FOLDER_MAX, 100),
};
PARAM_GROUP_DEFINE(BLOG, var_info);

static uint8_t blog_buffer[BLOG_BUFFER_SIZE];

static char* blog_tag = "[blog]";

struct rtp_blog {
    int fid;
    uint8_t file_open;
    char file_name[50];
    uint8_t log_status;
    blog_buffer_t buffer;

    Param_int32 blog_mode;
};

static struct rtp_blog blog;
/*-------------------------------------os-------------------------------------*/

/*----------------------------------function----------------------------------*/
static void _reset_log_buffer(void)
{
    blog.buffer.num_sector = BLOG_BUFFER_SIZE / BLOG_SECTOR_SIZE;
    blog.buffer.head = 0;
    blog.buffer.tail = 0;
    blog.buffer.index = 0;
}

static uint16_t _get_max_write_sector(uint32_t head_p, uint32_t tail_p)
{
    uint16_t sector_to_end;
    uint16_t sector_in_buffer;
    uint16_t sector_to_write;

    sector_to_end = blog.buffer.num_sector - tail_p;

    if (head_p >= tail_p) {
        sector_in_buffer = head_p - tail_p;
    } else {
        sector_in_buffer = sector_to_end + head_p;
    }

    sector_to_write = sector_in_buffer < sector_to_end ? sector_in_buffer : sector_to_end;

    return sector_to_write <= BLOG_MAX_SECTOR_TO_WRITE ? sector_to_write : BLOG_MAX_SECTOR_TO_WRITE;
}

static bool _buffer_check_full(uint32_t len_to_write)
{
    uint32_t free_space_in_sector = BLOG_SECTOR_SIZE - blog.buffer.index;

    // TODO: check if write multiple sectors at once

    /* check if buffer is full */
    if (free_space_in_sector < len_to_write) {
        if ((blog.buffer.head + 1) % blog.buffer.num_sector == blog.buffer.tail) {
            return true;
        }
    }

    return false;
}

static void _buffer_write(const uint8_t* data, uint16_t len)
{
    uint32_t free_space_in_sector = BLOG_SECTOR_SIZE - blog.buffer.index;

    // TODO: add support with len larger than BLOG_SECTOR_SIZE

    if (free_space_in_sector < len) {
        memcpy(&blog.buffer.data[blog.buffer.head * BLOG_SECTOR_SIZE + blog.buffer.index], data, free_space_in_sector);

        // move head point to next sector
        blog.buffer.head = (blog.buffer.head + 1) % blog.buffer.num_sector;
        blog.buffer.index = 0;

        memcpy(&blog.buffer.data[blog.buffer.head * BLOG_SECTOR_SIZE + blog.buffer.index], &data[free_space_in_sector], len - free_space_in_sector);
        blog.buffer.index += len - free_space_in_sector;
    } else {
        memcpy(&blog.buffer.data[blog.buffer.head * BLOG_SECTOR_SIZE + blog.buffer.index], data, len);
        blog.buffer.index += len;
    }
}

bool blog_write_critical_block(const void* payload, uint16_t len)
{
    int32_t bus_index;

    /* chceck log status */
    if (blog.log_status != BLOG_STATUS_LOGGING) {
        return false;
    }

    blog_take_mutex_block();

    /* check if buffer has enough space to store data */
    if (_buffer_check_full(len)) {
        TIMETAG_CHECK_EXECUTE(blog_buff_full1, 500, console_printf("%s buffer is full\n",blog_tag);)
        blog_release_mutex();
        return false;
    }

    /* write payload */
    _buffer_write(payload, len);

    blog_release_mutex();
    return true;
}

bool blog_write_file(const void* payload, uint16_t len)
{
    /* chceck log status */
    if (blog.log_status != BLOG_STATUS_LOGGING) {
        return false;
    }

    if (!blog.file_open) {
        /* no log file is opened */
        return false;
    }

    int bw = write(blog.fid, payload, len);

    return (bw == len);
}

gp_err blog_start(char* file_name)
{
    if (blog.log_status != BLOG_STATUS_IDLE) {
        if (blog.blog_mode != 1) {
            console_printf("%s %s is logging, stop it first\n", blog_tag, blog.file_name);
        }
        return GP_EBUSY;
    }

    /*********************** create log file ***********************/
    blog.fid = open(file_name, O_CREAT | O_WRONLY);

    if (blog.fid < 0) {
        console_printf("%s %s open fail\n", blog_tag, file_name);
        return GP_ERROR;
    }

    /* set log file open flag */
    blog.file_open = 1;

    /*********************** init log buffer ***********************/
    blog.buffer.head = blog.buffer.tail = 0;
    blog.buffer.index = 0;

    /*********************** write log header ***********************/
    blog.log_status = BLOG_STATUS_WRITE_HEAD;

    aplog_start();

    /*********************** set log status ***********************/
    strncpy(blog.file_name, file_name, sizeof(blog.file_name) - 1);

    // start logging, set flag
    blog.log_status = BLOG_STATUS_LOGGING;

#if CONFIG_HAL_BOARD == HAL_BOARD_SITL_WIN
    console_printf("%s start logging:%s\n", blog_tag, file_name);
#endif

    return GP_EOK;
}

void blog_stop(void)
{
    if (blog.log_status == BLOG_STATUS_LOGGING) {
        // set log status to stopping, let logger thread write reamined data in
        // buffer, then stop logging.
        blog.log_status = BLOG_STATUS_STOPPING;
        aplog_stop();
    }
}

void blog_flush(void)
{
    if (!blog.file_open || blog.fid < 0) {
        /* no log file is opened */
        return;
    }

    fsync(blog.fid);
}

void blog_async_output(void)
{
    /* chceck log status */
    if (blog.log_status != BLOG_STATUS_LOGGING && blog.log_status != BLOG_STATUS_STOPPING) {
        return;
    }

    uint32_t head_p, tail_p;
    uint8_t need_sync = 0;

    if (!blog.file_open) {
        // no log file is opened
        return;
    }

    aplog_write_more_startup_messages();

    head_p = blog.buffer.head;
    tail_p = blog.buffer.tail;

    need_sync = (head_p != tail_p);

    // write log buffer sector into storage device 
    while (head_p != tail_p) {
        uint16_t sector_to_write = _get_max_write_sector(head_p, tail_p);

        write(blog.fid, &blog.buffer.data[tail_p * BLOG_SECTOR_SIZE], sector_to_write * BLOG_SECTOR_SIZE);

        tail_p = (tail_p + sector_to_write) % blog.buffer.num_sector;

        blog.buffer.tail = tail_p;
    }

    if (need_sync) {
        fsync(blog.fid);
    }

    // if logging is off, we need to clean up buffer.
    if (blog.log_status == BLOG_STATUS_STOPPING) {
        /* write rest data in buffer */
        if (blog.buffer.index) {
            write(blog.fid, &blog.buffer.data[tail_p * BLOG_SECTOR_SIZE], blog.buffer.index);
            fsync(blog.fid);
        }

        if (blog.file_open) {
            close(blog.fid);
            blog.fid = -1;
            blog.file_open = 0;
        }

        // set log status to idle
        blog.log_status = BLOG_STATUS_IDLE;

#if CONFIG_HAL_BOARD == HAL_BOARD_SITL_WIN
        console_printf("%s stop logging:%s\n", blog_tag, blog.file_name);
#endif
    }
}

uint8_t blog_get_status(void)
{
    return blog.log_status;
}

char* blog_get_logging_file_name(void)
{
    return blog.file_name;
}

int32_t blog_get_log_mode()
{
    return blog.blog_mode;
}

void blog_init(void)
{
    uint32_t i;

    param_link_variable(PARAM_ID(BLOG, BLOG_MODE), &blog.blog_mode);

    blog.file_open = 0;
    blog.log_status = BLOG_STATUS_IDLE;

    // initialize log buffer
    blog.buffer.data = &blog_buffer[0];
    if (blog.buffer.data == NULL) {
        console_printf("%s blog buffer malloc fail\n", blog_tag);
    }

    _reset_log_buffer();

    blog_init_mutex();
}

/*------------------------------------test------------------------------------*/


